﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using Lilium.Collections;
using System.Linq.Expressions;
using Lilium.Linq;
using Lilium.Web.Mvc.State;
using Lilium.Web.Mvc.Metadata;

namespace Lilium.Web.Mvc
{
	/// <summary>
	/// Создает выражение для упорядочивания данных посредством обработки кликов на нажатие колонок.
	/// Один клик = упорядочивание по возрастанию. Клик по той же колонке - упорядочивание по убыванию, 
	/// клик по новой колонке - добавление нового выражения порядка в очередь.
	/// </summary>
	public sealed class SortExpressionBuilder : IStatefulObject
	{
		public SortExpressionBuilder(ModelMetadata modelMetadata)
		{
			if (modelMetadata == null) throw new ArgumentNullException("modelMetadata");

			m_ModelMetadata = modelMetadata;
			m_PropertyQueue = new CyclicQueue<SortExpressionEntry>(modelMetadata.Properties.Count());
		}

		public IQueryable<TEntity> AppendSortExpression<TEntity>(IQueryable<TEntity> query)
		{
			if (query == null) throw new ArgumentNullException("query");
			using (var enumerator = PropertyQueue.GetReversedEnumerator())
			{
				if (enumerator.MoveNext())
				{
					query = query.Provider.CreateQuery<TEntity>(Expression.Call(
						typeof(Queryable),
						enumerator.Current.Descending ? "OrderByDescending" : "OrderBy",
						new[] { typeof(TEntity), enumerator.Current.MemberAccessExpression.Type },
						new[] { query.Expression, Expression.Quote(Expression.Lambda(enumerator.Current.MemberAccessExpression, enumerator.Current.ParameterExpression)) }));
				}

				while (enumerator.MoveNext())
				{
					query = query.Provider.CreateQuery<TEntity>(Expression.Call(
						typeof(Queryable),
						enumerator.Current.Descending ? "ThenByDescending" : "ThenBy",
						new[] { typeof(TEntity), enumerator.Current.MemberAccessExpression.Type },
						new[] { query.Expression, Expression.Quote(Expression.Lambda(enumerator.Current.MemberAccessExpression, enumerator.Current.ParameterExpression)) }));
					// methodAsc = "ThenBy"; methodDesc = "ThenByDescending"; --- 
					// throw new NotImplementedException("public IQueryable<TEntity> AppendSortExpression<TEntity>(IQueryable<TEntity> query)");
				}
			}
			return query;
		}

		public void SortCommand(string propertyPath)
		{
			if (propertyPath == null) throw new ArgumentNullException("propertyPath");

			SortExpressionEntry entry;

			if (PropertyQueue.TryPeekLast(out entry) && (entry.PropertyPath == propertyPath))
			{
				entry.Descending = !entry.Descending;
			}
			else
			{
				var parameterExpression = Expression.Parameter(ModelMetadata.ModelType, "p0");
				var memberAccessExpression = BuildMemberAccessExpression(parameterExpression, ModelMetadata, propertyPath.Split('.').Cast<string>().GetEnumerator());

				PropertyQueue.Enqueue(new SortExpressionEntry(propertyPath, memberAccessExpression, parameterExpression));
			}
		}

		#region IConsumeControlState Members

		string IStatefulObject.Key
		{
			get { return typeof(SortExpressionBuilder).AssemblyQualifiedName; }
		}

		void IStatefulObject.SetState(object state)
		{
			if (state == null) throw new ArgumentNullException("state");

			var typedState = (KeyValuePair<ModelMetadata, CyclicQueue<SortExpressionEntry>>)state;

			m_ModelMetadata = typedState.Key;
			m_PropertyQueue = typedState.Value;
		}

        object IStatefulObject.GetState(string actionName)
		{
			return new KeyValuePair<ModelMetadata, CyclicQueue<SortExpressionEntry>>(ModelMetadata, PropertyQueue);
		}

		#endregion

		private Expression BuildMemberAccessExpression(Expression source, ModelMetadata modelMetadata, IEnumerator<string> path)
		{
			if (source == null) throw new ArgumentNullException("source");
			if (modelMetadata == null) throw new ArgumentNullException("modelMetadata");
			if (path == null) throw new ArgumentNullException("chain");

			if (path.MoveNext())
			{
				var propertyMetadata = modelMetadata.Properties.FirstOrDefault(pm => pm.PropertyName == path.Current);

				if (propertyMetadata == null)
					throw new ArgumentException(string.Format("Property {0} not found on type {1}", path.Current, modelMetadata.DisplayName));

				return BuildMemberAccessExpression(Expression.PropertyOrField(source, path.Current), propertyMetadata, path);
			}
			else
			{	
				var associationExtension = modelMetadata.FindExtension<AssociationMetadataExtension>();
				if (associationExtension != null)
				{	// добавляем акцессор к ссылке на сущность
					var propertyName =
						!string.IsNullOrEmpty(associationExtension.OtherOrder) ? associationExtension.OtherOrder :
						!string.IsNullOrEmpty(associationExtension.OtherName) ? associationExtension.OtherName :
						associationExtension.OtherKey;

					var propertyMetadata = modelMetadata.Properties.FirstOrDefault(pm => pm.PropertyName == propertyName);
					
					if (propertyMetadata == null)
						throw new ArgumentException(string.Format("Property {0} not found in model {1}", path.Current, modelMetadata.DisplayName));

					return Expression.PropertyOrField(source, propertyMetadata.PropertyName);
				}
				else
					return source;
			}
		}

		#region private CyclicQueue<SortExpressionEntry> PropertyQueue

		private CyclicQueue<SortExpressionEntry> m_PropertyQueue;

		private CyclicQueue<SortExpressionEntry> PropertyQueue
		{
			get
			{
				return m_PropertyQueue;
			}
		}

		#endregion

		#region private ModelMetadata ModelMetadata

		private ModelMetadata m_ModelMetadata;

		private ModelMetadata ModelMetadata
		{
			get
			{
				return m_ModelMetadata;
			}
		}

		#endregion

		private sealed class SortExpressionEntry
		{
			public SortExpressionEntry(string propertyPath, Expression memberAccessExpression, ParameterExpression parameterExpression)
			{
				if (propertyPath == null) throw new ArgumentNullException("propertyPath");
				if (memberAccessExpression == null) throw new ArgumentNullException("memberAccessExpression");
				if (parameterExpression == null) throw new ArgumentNullException("parameterExpression");

				m_MemberAccessExpression = memberAccessExpression;
				m_ParameterExpression = parameterExpression;
				m_PropertyPath = propertyPath;
			}

			#region public bool Descending

			private bool m_Descending;

			public bool Descending
			{
				get
				{
					return m_Descending;
				}
				set
				{
					m_Descending = value;
				}
			}

			#endregion

			#region public Expression MemberAccessExpression

			private readonly Expression m_MemberAccessExpression;

			public Expression MemberAccessExpression
			{
				get
				{
					return m_MemberAccessExpression;
				}
			}

			#endregion

			#region public Expression ParameterExpression

			private readonly ParameterExpression m_ParameterExpression;

			public ParameterExpression ParameterExpression
			{
				get
				{
					return m_ParameterExpression;
				}
			}

			#endregion

			#region public string PropertyPath

			private readonly string m_PropertyPath;

			public string PropertyPath
			{
				get
				{
					return m_PropertyPath;
				}
			}

			#endregion
		}

		#region IStatefulObject Members

		public Type StateType
		{
			get { throw new NotImplementedException(); }
		}

		#endregion
	}
}